#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "GraphView.h"

#include "Class.h"
#include "ObjList.h"
#include "IdDictionary.h"
#include "OrdColl.h"
#include "Dialog.h"
#include "TextItem.h"
#include "Math.h"

//---- GraphNode ---------------------------------------------------------------

NewMetaImpl(GraphNode, TreeNode, (TP(constrainingNodes), TP(nonTreeOutEdges),
								T(inEdges)));

GraphNode::GraphNode(int i, Collection *cp) : TreeNode(i, cp)
{
	constrainingNodes= nonTreeOutEdges= 0;
	inEdges= 0;
}

GraphNode::~GraphNode()
{
	SafeDelete(nonTreeOutEdges);

	// clean up contents of GraphNode to avoid duplicate deletes from
	// CompositeVObject::~CompositeVObject
	if (Size() > 1) {
		SeqCollection *l= GetList();
		Iter next(l);
		next();    // skip first
		Object *op;
		while (op= next())
			l->RemovePtr(op);
	}
}

VObject *GraphNode::Image()
{
	if (Size() > 0)
		return At(0);
	return 0;
}

int GraphNode::CalcShift()
{
	Point origin(contentRect.origin);

	if (InCalcShift()) {
		Warning("CalcShift", "cycle in graph");
		return contentRect.origin.x;
	}
	SetFlag(eGraphInCalc);
	if (!IsPositionSet()) {
		SetFlag(eGraphPosSet);
		if (constrainingNodes) {
			Iter next(constrainingNodes);
			GraphNode *gp;
			while (gp= (GraphNode*)next())
				origin.x= Math::Max(origin.x, gp->CalcShift());
			//if (origin.x != contentRect.origin.x)
				origin.x += 2*(Gap().x);
		}
		SafeDelete(constrainingNodes);
	}
	if (origin != contentRect.origin)
		SetOrigin(origin);
		
	ResetFlag(eGraphInCalc);
	return contentRect.origin.x+contentRect.extent.x;
}

void GraphNode::AddNonTreeOutEdge(GraphNode *gp)
{
	if (nonTreeOutEdges == 0)
		nonTreeOutEdges= new OrdCollection;
	nonTreeOutEdges->Add(gp);
}

void GraphNode::AddConstrainingNode(GraphNode *gp)
{
	if (constrainingNodes == 0)
		constrainingNodes= new OrdCollection;
	constrainingNodes->Add(gp);
}

void GraphNode::AddNode(VObject *vp)
{
	Add(vp);
}

void GraphNode::Draw(Rectangle r)
{
	Image()->DrawAll(r, FALSE);
}

void GraphNode::DrawConnections()
{
	TreeNode::DrawConnections();
	if (nonTreeOutEdges) {
		VObject *image= Image();
		Point p1= image->contentRect.E();
		Iter next(nonTreeOutEdges);
		GraphNode *gp;
		while (gp= (GraphNode*)next())
			GrLine(p1, gp->Image()->contentRect.W());
	}
}

void GraphNode::Export(OStream &s)
{
	// leave node
	if ((Size() + (nonTreeOutEdges ? nonTreeOutEdges->Size() : 0)) == 1)
		return;
		
	GraphNode *gp;
	int i= 0;
	if (Size() > 0) {
		VObjectIter next1(this);
		while (gp= (GraphNode*)next1()) {
			if (i++ == 0) // the image of the node itself
				s << gp->AsString() << ": ";
			else
				s << gp->Image()->AsString() SP;
		}
	}
	if (nonTreeOutEdges) {
		Iter next2(nonTreeOutEdges);
		while (gp= (GraphNode*)next2())
			s << gp->Image()->AsString() SP;
	}
	s NL;
}

//---- GraphNodeMover ----------------------------------------------------------

class GraphNodeMover: public Command {
	GraphView *gvp;
	GraphNode *gnp;
	VObject *item;
	GrCursor oldcursor;
	Point delta;
public:
	GraphNodeMover(GraphView *g, GraphNode *v) : Command(cIdNone, "move node")
		{ gvp= g; gnp= v; item= gnp->Image(); delta= 0; }
	void TrackFeedback(Point, Point, bool);
	Command *TrackMouse(TrackPhase, Point, Point, Point);
	void DoIt();
	void UndoIt();
};

void GraphNodeMover::TrackFeedback(Point anchorPoint, Point nextPoint, bool)
{
	item->Outline(nextPoint - anchorPoint);
}

Command *GraphNodeMover::TrackMouse(TrackPhase tp, Point ap, Point, Point np)
{
	switch (tp) {
	case eTrackPress:
		oldcursor= GrGetCursor();
		break;
	case eTrackMove:
		GrSetCursor(eCrsMoveHand);
		break;
	case eTrackRelease:
		delta= np-ap;
		GrSetCursor(oldcursor);
		break;
	default:
		break;
	}
	return this;

}

void GraphNodeMover::DoIt()
{
	item->Move(delta);
	gnp->contentRect+= delta;
	gvp->CalcExtent();
	gvp->ForceRedraw();
}

void GraphNodeMover::UndoIt()
{
	item->Move(-delta);
	gnp->contentRect-= delta;
	gvp->CalcExtent();
	gvp->ForceRedraw();
}

//---- GraphView ---------------------------------------------------------------

NewMetaImpl(GraphView, TreeView, (TP(nodes), TP(paths), TP(refs)));

GraphView::GraphView(EvtHandler *dp) : TreeView(dp)
{
	nodes= new IdDictionary;
	connType= eTCDiagonal;
	paths= new OrdCollection;
	refs= new OrdCollection;
}

GraphView::~GraphView()
{
	if (nodes) {
		nodes->FreeValues();
		SafeDelete(nodes);
	}
	SafeDelete(paths);
	SafeDelete(refs);
}

void GraphView::Draw(Rectangle r)
{
	DictionaryIterator it(nodes);
	register GraphNode *np;

	View::Draw(r);
	if (connType != eTCNone) {
		while (np= (GraphNode*)it.NextValue())
			np->DrawConnections();
	}
		
	DrawPaths(r);
	DrawReferences(r);
		
	it.Reset();
	while (np= (GraphNode*)it.NextValue()) {
		VObject *vp= np->Image();
		if (vp && vp->contentRect.Intersects(r))
			vp->DrawAll(r, vp == GetSelection());
	}
}

void GraphView::SetConnType(TreeConnection ct)
{
	if (ct != eTCNone && ct != eTCDiagonal)
		return;
	connType= ct;
	ForceRedraw();
}

GraphNode *GraphView::FindNode(Point lp)
{
	DictionaryIterator it(nodes);
	GraphNode *np= 0;

	while (np= (GraphNode*)it.NextValue()) {
		VObject *vp= np->Image();
		if (vp->ContainsPoint(lp))
			return np;
	}
	return 0;
}

Command *GraphView::DoLeftButtonDownCommand(Point lp, Token, int cl)
{
	GraphNode *np= FindNode(lp);
	if (np)
		return new TreeNodeSelector(np, cl);
	return gNoChanges;
}

Command *GraphView::DoMiddleButtonDownCommand(Point lp, Token, int)
{
	GraphNode *np= FindNode(lp);
	if (np)
		return new GraphNodeMover(this, np);
	return gNoChanges;
}

void GraphView::CalcExtent()
{
	DictionaryIterator it(nodes);
	register GraphNode *np;
	Rectangle r;

	while (np= (GraphNode*)it.NextValue())
		r.Merge(np->Image()->contentRect);
	SetExtent(r.extent+Point(200));
}

Iterator *GraphView::MakeSubPartsIter(Object*)
{
	AbstractMethod("MakeSubPartsIter");
	return 0;
}

Iterator *GraphView::MakeChildrenIter(Object *op)
{
	return MakeSubPartsIter(op);
}

VObject *GraphView::DoCreateRoot()
{
	return new TextItem("Root");
}

VObject *GraphView::DoCreateDialog()
{
	return 0;
}

VObject *GraphView::AssociatedVObject(Object *op)
{
	GraphNode *gp= (GraphNode*) nodes->AtKey(op);
	if (gp)
		return gp->Image();
	return 0;
}

GraphNode *GraphView::AssociatedGraphNode(Object *op)
{
	return (GraphNode*) nodes->AtKey(op);
}

void GraphView::SetGraph(GraphNode *root, bool)
{
	DictionaryIterator it(nodes);
	GraphNode *np;

	while (np= (GraphNode*)it.NextValue()) {
		np->SetContainer(this);
		np->Enable();
	}
	// calculate tree layout
	root->CalcExtent();
	root->SetOrigin(GetOrigin());
	it.Reset();
	while (np= (GraphNode*)it.NextValue())
		np->CalcShift();
	CalcExtent();
	ForceRedraw();
}

void GraphView::EmptyGraph()
{
	SetSelection(0);
	nodes->FreeValues();
	nodes->Empty(0);
}

GraphNode *GraphView::BuildGraphDFS(Object *np)
{
	Object *subNode;
	GraphNode *gp;

	if (gp= (GraphNode*) nodes->AtKey(np))
		return gp;

	GraphNode *node= new GraphNode;
	node->AddNode(NodeAsVObject(np));
	nodes->PutAtKeyIfAbsent(node, np); // register node in dictionary
	Iterator *it= MakeSubPartsIter(np);

	if (it) {
		Iter next(it);
		
		while (subNode= next()) {
			// node already occurred hence must be a nonTreeOutEdge
			if (gp= (GraphNode*) nodes->AtKey(subNode)) { // node already occurred, -> nonTreeOutEdge
				node->AddNonTreeOutEdge(gp);
				gp->AddConstrainingNode(node);
			} else {
				gp= BuildGraphDFS(subNode);
				node->AddNode(gp);
			}
			gp->AddInEdge(node);
		}
	}
	return node;
}

void GraphView::BuildGraphBFS(Object *op)
{
	ObjList q;          // queue for breadth first search
	GraphNode *gnp, *snp;
	Object *subnode, *node;

	q.Add(op);
	while (q.Size() > 0) {
		node= q.RemoveFirst();
		if ((gnp= (GraphNode*) nodes->AtKey(node)) == 0) {
			gnp= new GraphNode;
			nodes->PutAtKeyIfAbsent(gnp, node);
			gnp->AddNode(NodeAsVObject(node));
		}
		Iterator *it= MakeSubPartsIter(node);
		if (it) {
			Iter next(it);
			while (subnode= next()) {
				// node was already visited
				if (snp= (GraphNode*)nodes->AtKey(subnode)) {
					gnp->AddNonTreeOutEdge(snp);
					snp->AddConstrainingNode(gnp);
				} else {
					snp= new GraphNode();
					snp->AddNode(NodeAsVObject(subnode));
					gnp->AddNode(snp);
					nodes->PutAtKeyIfAbsent(snp, subnode);
					q.Add(subnode);
				}
				snp->AddInEdge(gnp);
			}
		}
	}
}

GraphNode *GraphView::FindRoot()
{
	OrdCollection *oc= new OrdCollection();
	DictionaryIterator it(nodes);
	GraphNode *np, *root;

	while (np= (GraphNode *)it.NextValue())
		if (np->NumInEdges() == 0)
			oc->Add(np);
		
	if (oc->Size() == 1) {
		root= (GraphNode*)oc->First();
		SafeDelete(oc);
		return root;
	}
	// create artificial root
	VObject *vop= DoCreateRoot();
	oc->AddFirst(vop);
	root= new GraphNode(cIdNone, oc);
	nodes->PutAtKeyIfAbsent(root, vop);
	return root;
}

void GraphView::DrawPaths(Rectangle r)
{
	paths->ForEach(GraphPath,Draw)(r);
}

void GraphView::AddPath(GraphPath *p)
{
	paths->Add(p);
	InvalidateRect(p->BBox());
}

GraphPath *GraphView::RemovePath(GraphPath *p)
{
	GraphPath *gp= (GraphPath*)paths->RemovePtr(p);
	if (gp)
		InvalidateRect(p->BBox());
	return gp;
}

void GraphView::RemoveAllPaths()
{
	paths->FreeAll();
	paths->Empty(0);
	ForceRedraw();
}

void GraphView::DrawReferences(Rectangle r)
{
	refs->ForEach(GraphReference,Draw)(r);
}

void GraphView::AddReference(GraphReference *p)
{
	refs->Add(p);
	InvalidateRect(p->BBox());
}

GraphReference *GraphView::RemoveReference(GraphReference *p)
{
	GraphReference *gp= (GraphReference*)refs->RemovePtr(p);
	if (gp)
		InvalidateRect(p->BBox());
	return gp;
}

void GraphView::RemoveAllReferences()
{
	refs->FreeAll();
	refs->Empty(0);
	ForceRedraw();
}

//---- GraphPath ---------------------------------------------------------------

GraphPath::GraphPath(GraphView *g, Collection *n, Ink *i, int w, bool f)
{
	nodes= n;
	free= f;
	gvp= g;
	width= w;
	ink= i;
}
		
GraphPath::~GraphPath()
{
	if (free)
		SafeDelete(nodes);
}

void GraphPath::Draw(Rectangle)
{
	register Object *op;
	Iter next(nodes);

	for (int i= 0; op= next(); i++) {
		register VObject *image= gvp->AssociatedVObject(op);
		if (!image)
			continue;//Error("Draw", "node in GraphPath without representation");
		GrSetPenNormal();
		GrStrokeRect(image->contentRect.Expand(1));
		GrSetPenSize(width);
		GrSetPenInk(ink);
		if (i == 0)
			GrMoveto(image->contentRect.Center());
		else
			GrLineto(image->contentRect.Center());
	}
}

Rectangle GraphPath::BBox()
{
	Rectangle r;
	Object *op;
	VObject *image;
	Iter next(nodes);
	while (op= next())
		if (image= gvp->AssociatedVObject(op))
			r.Merge(image->contentRect.Expand(4));
	return r;
}

//---- GraphReference ----------------------------------------------------------

GraphReference::GraphReference(GraphView *g, Object *op, Collection *n, Ink *i,
								int w, bool f)
{
	nodes= n;
	free= f;
	gvp= g;
	width= w;
	ink= i;
	referto= op;
}
		
GraphReference::~GraphReference()
{
	if (free)
		SafeDelete(nodes);
}

void GraphReference::Draw(Rectangle)
{
	register Object *op;
	Iter next(nodes);
	VObject *opimage= gvp->AssociatedVObject(referto);
	if (!opimage)
		return;
	GrSetPenNormal();
	GrStrokeRect(opimage->contentRect.Expand(1));

	for (int i= 0; op= next(); i++) {
		VObject *image= gvp->AssociatedVObject(op);
		if (!image)
			continue;
		GrSetPenNormal();
		GrStrokeRect(image->contentRect.Expand(1));
		DrawConnection(i, opimage, image);
	}
}

void GraphReference::DrawConnection(int, VObject *from, VObject *to)
{
	GrSetPenSize(width);
	GrSetPenInk(ink);
	GrLine(from->contentRect.Center(), to->contentRect.Center());
}

Rectangle GraphReference::BBox()
{
	register Object *op;
	register VObject *image;
	Iter next(nodes);
	Rectangle r;

	VObject *opimage= gvp->AssociatedVObject(referto);
	while (opimage && (op= next())) {
		if (image= gvp->AssociatedVObject(op)) {
			r.Merge(opimage->contentRect.Expand(4));
			r.Merge(image->contentRect.Expand(4));
		}
	}
	return r;
}

